﻿using System;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using Honeycomb.Protocol.Messages;
using Honeycomb.Transport;

namespace Honeycomb.Protocol
{
    /// <summary>
    /// Base class for protocol client and protocol peer.
    /// </summary>
    public abstract class ProtocolBase : IDisposable
    {
        /// <summary>
        /// INetworkPeer implementation.
        /// </summary>
        private readonly INetworkPeer networkPeer;

        /// <summary>
        /// Stream for network communication.
        /// </summary>
        private readonly NetworkStream stream;

        /// <summary>
        /// Binary formatter for serialization.
        /// </summary>
        private readonly BinaryFormatter formatter = new BinaryFormatter();

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="networkPeer">INetworkPeer implementation.</param>
        protected ProtocolBase(INetworkPeer networkPeer)
        {
            if (networkPeer == null)
                throw new ArgumentNullException("networkPeer");

            this.networkPeer = networkPeer;
            stream = networkPeer.GetStream();
        }

        /// <summary>
        /// Send message.
        /// </summary>
        /// <typeparam name="TMessage">Type of message.</typeparam>
        /// <param name="message">Message instance.</param>
        protected void Send<TMessage>(TMessage message) where TMessage : IMessage
        {
            formatter.Serialize(stream, message);
        }

        /// <summary>
        /// Read message.
        /// </summary>
        /// <typeparam name="TMessage">Type of message.</typeparam>
        /// <returns>Read message.</returns>
        protected TMessage Read<TMessage>() where TMessage : IMessage
        {
            return (TMessage) Read(typeof (TMessage));
        }

        /// <summary>
        /// Read message of specified type.
        /// </summary>
        /// <param name="allowedMessageTypes">Allowed message types.</param>
        /// <returns>Message instance.</returns>
        protected object Read(params Type[] allowedMessageTypes)
        {
            if (allowedMessageTypes == null)
                throw new ArgumentNullException("allowedMessageTypes");

            // Get a deserialized object from the network stream.
            object message = formatter.Deserialize(stream);

            // Make sure it is allowed.
            foreach (Type allowedMessageType in allowedMessageTypes)
                if (message.GetType() == allowedMessageType)
                    return message;

            throw new InvalidOperationException("The other peer sent a invalid message type.");
        }

        /// <summary>
        /// Gets the network address of the Client/Peer.
        /// </summary>
        public string Address
        {
            get { return networkPeer.Address; }
        }

        /// <summary>
        /// Dispose.
        /// </summary>
        public void Dispose()
        {
            stream.Dispose();
            networkPeer.Dispose();
        }
    }
}